﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace ICodeShare.UI.Controls
{
    /// <summary>
    /// 跳动的数字控件
    /// </summary>
    [TemplatePart(Name = PART_LayoutRoot, Type = typeof(Grid))]
    [TemplatePart(Name = PART_DigitImage, Type = typeof(Image))]
    public class OdometerDigit : ContentControl
    {
        #region Constant

        //引用模板元素
        private const string PART_LayoutRoot = "PART_LayoutRoot"; //主要部分

        private const string PART_DigitImage = "PART_DigitImage"; //主要部分

        #endregion Constant

        #region Fields

        /// <summary>
        /// Value of the digit
        /// </summary>
        private int digit;

        private Grid gridLayoutRoot;
        private Image imageDigit;

        #endregion Fields

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether this instance is loaded.
        /// </summary>
        /// <value><c>true</c> if this instance is loaded; otherwise, <c>false</c>.</value>
        public bool DashboardLoaded
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the current value of the digit 0..9
        /// </summary>
        public int Digit
        {
            get { return this.digit; }
        }

        #endregion Properties

        #region Constructors

        static OdometerDigit()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(OdometerDigit), new FrameworkPropertyMetadata(typeof(OdometerDigit)));
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            gridLayoutRoot = GetTemplateChild(PART_LayoutRoot) as Grid;
            gridLayoutRoot.Clip = new RectangleGeometry { Rect = new Rect { X = 0, Y = 0, Width = 40, Height = 50 } };
            imageDigit = GetTemplateChild(PART_DigitImage) as Image;
            if (AnimateIndicatorStoryboard == null)
            {
                AnimateIndicatorStoryboard = gridLayoutRoot.TryFindResource("ImageStoryboard") as Storyboard;
                AnimateIndicatorStoryboard.Completed += new EventHandler(this._swipe_Completed);
            }
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            Loaded += new RoutedEventHandler(this.OdometerDigit_Loaded);
        }

        #endregion Constructors

        #region Events

        /// <summary>
        /// This digit is just about to roll under from 0 to 9, this event
        /// allows the next least significant digit to decrement by one at
        /// the same time and gives that nice Odometer behaviour
        /// </summary>
        public event EventHandler<EventArgs> DecadeMinus;

        /// <summary>
        /// This digit is just about to roll over from 9 to 0, this event
        /// allows the next most significant digit to increment by one at
        /// the same time and gives that nice Odometer behaviour
        /// </summary>
        public event EventHandler<EventArgs> DecadePlus;

        #endregion Events

        #region Methods

        /// <summary>
        /// Slides the image down by 32 to show the value decrementing.On amination complete
        /// calls MoveToDigit to ensure the digit is nailed. if the new digit
        /// is 9 we raise the DecadeMinusEvent a second OdometerDigit may listen for this event and
        /// decrment itself as a result. When decrementing from 1000 to 999 this will be 'fun'
        /// </summary>
        public void Decrement()
        {
            if (imageDigit != null)
            {
                this.digit -= 1;
                if (this.digit < 0)
                {
                    this.digit = 9;
                    this.OnDecadeMinus();
                }

                double from = (double)imageDigit.GetValue(Canvas.TopProperty);
                double to = from + 32;

                GetChildDoubleAnimation(AnimateIndicatorStoryboard, "_anim").To = to;
                GetChildDoubleAnimation(AnimateIndicatorStoryboard, "_anim").From = from;

                StartStoryboard(AnimateIndicatorStoryboard);
            }
        }

        /// <summary>
        /// Slides the image up by 32 to show the value incrementing. On amination complete
        /// calls MoveToDigit to ensure the digit is nailed. if the new digit
        /// is 0 we raise the DecadePlusEvent a second OdometerDigit may listen for this event and
        /// increment itself as a result. When incrementing from 999 to 1000 this can cascade quite
        /// some way
        /// </summary>
        public void Increment()
        {
            if (imageDigit != null)
            {
                this.digit += 1;
                if (this.digit > 9)
                {
                    this.digit = 0;
                    this.OnDecadePlus();
                }

                double from = (double)imageDigit.GetValue(Canvas.TopProperty);

                // move to the lower 0 in the animation it has the roll over ability
                if (this.digit == 0)
                {
                    double offset = 23;
                    double amountToScroll = 9 * 32;
                    imageDigit.SetValue(Canvas.TopProperty, -(offset + amountToScroll));
                    from = -(offset + amountToScroll);

                    Storyboard sb = gridLayoutRoot.TryFindResource("ImageStoryboard") as Storyboard;
                    StartStoryboard(sb);
                }

                double to = from - 32;
                GetChildDoubleAnimation(AnimateIndicatorStoryboard, "_anim").To = to;
                GetChildDoubleAnimation(AnimateIndicatorStoryboard, "_anim").From = from;

                StartStoryboard(AnimateIndicatorStoryboard);
            }
        }

        /// <summary>
        /// When chained together in an odeometer the container connects the digits togeter
        /// so when a lower order digit rolls under to 9 the upper digit (us) can decrement.
        /// </summary>
        /// <param name="sender">the lower digit</param>
        /// <param name="args">Empty args</param>
        public void LowerOrderDigitDecadeMinus(object sender, EventArgs args)
        {
            this.Decrement();
        }

        /// <summary>
        /// When chained together in an odeometer the container connects the digits togeter
        /// so when a lower order digit rolls over to 0 the upper digit (us) can increment.
        /// </summary>
        /// <param name="sender">the lower digit</param>
        /// <param name="args">Empty args</param>
        public void LowerOrderDigitDecadePlus(object sender, EventArgs args)
        {
            this.Increment();
        }

        /// <summary>
        /// Called from the odometer control to set the initial values
        /// </summary>
        /// <param name="value">The value of the digit</param>
        internal void SetInitialValue(int value)
        {
            this.digit = value;
            this.MoveToDigit();
        }

        /// <summary>
        /// Manifests the changes.
        /// </summary>
        private void ManifestChanges()
        {
        }

        /// <summary>
        /// Place the image so the specified image is revealed
        /// </summary>
        private void MoveToDigit()
        {
            if (imageDigit != null)
            {
                double offset = 23;
                double amountToScroll = this.digit * 32;
                imageDigit.SetValue(Canvas.TopProperty, -(offset + amountToScroll));
            }
        }

        /// <summary>
        /// Handles the Loaded event of the OdometerDigit control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OdometerDigit_Loaded(object sender, RoutedEventArgs e)
        {
            this.ManifestChanges();
            this.DashboardLoaded = true;
        }

        /// <summary>
        /// we are rolling over from 9 to 0
        /// </summary>
        private void OnDecadeMinus()
        {
            if (this.DecadeMinus != null)
            {
                this.DecadeMinus(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// We are rolling down from 0 to 9 inform upper digit to decrement
        /// </summary>
        private void OnDecadePlus()
        {
            if (this.DecadePlus != null)
            {
                this.DecadePlus(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Handles the Completed event of the _swipe control.  Moves the image to the correct cannonical location,
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void _swipe_Completed(object sender, EventArgs e)
        {
            this.MoveToDigit();
        }

        #endregion Methods

        #region Animation

        public Storyboard AnimateIndicatorStoryboard { get; set; }

        //public void ImageDoubleAnimation()
        //{
        //}
        /// <summary>
        /// Gets a DoubleAnimation from the children of a storyboard by name
        /// </summary>
        /// <param name="storyboard">The story board.</param>
        /// <param name="name">The name of the point animation.</param>
        /// <returns>The requesed double animation</returns>
        protected DoubleAnimation GetChildDoubleAnimation(Storyboard storyboard, string name)
        {
            foreach (DoubleAnimation da in storyboard.Children)
            {
                if (da.Name == name)
                {
                    return da as DoubleAnimation;
                }
            }
            return null;
        }

        /// <summary>
        /// Starts the specified story board, this differs between Silverlight and
        /// WPF because under WPF we have to specify the animation is controllable
        /// because a value change may force us to alter the animation endpoint.
        /// </summary>
        /// <param name="sb">The storyboard.</param>
        protected void StartStoryboard(Storyboard sb)
        {
            sb.Begin(gridLayoutRoot, true);
        }

        #endregion Animation
    }
}